/**
 * <b>项目名：</b>亲子分享-服务端项目<br/>
 * <b>包名：</b>com.saas.dao<br/>
 * <b>文件名：</b>MongoDAO.java<br/>
 * <b>版本信息：</b>V1.0<br/>
 * <b>日期：</b>2014-9-1-下午03:03:48<br/>
 * <b>作者：</b><br/>
 * <b>Copyright (c)</b> 2014坚果(北京)科技有限公司-版权所有<br/>
 */
package com.saas.dao;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.mongodb.*;
import com.mongodb.client.MongoDatabase;
import com.mongodb.util.JSON;
import com.saas.exception.ServiceException;
import com.saas.util.*;
import org.apache.commons.lang.StringUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 
 * <b>类名称：</b>MongoDAO<br/>
 * <b>类描述：</b><br/>
 * <b>创建人：</b><br/>
 * <b>修改人：</b><br/>
 * <b>修改时间：</b>2014-9-1 下午03:03:48<br/>
 * <b>修改备注：</b><br/>
 * 
 * @version 1.0.0<br/>
 * 
 */
@Component
@SuppressWarnings("deprecation")
public class MongoDAO {
//	@Autowired
//	private CacheService cacheService;
	
	public static DB db;
	public static MongoDatabase saasDatabase;

	protected static Logger logger = LoggerFactory.getLogger(MongoDAO.class);
	
	static {
		db = MongoDBConnection.getMongoClient().getDB(SaasConfig.mongoDBName);
		saasDatabase= MongoDBConnection.getMongoClient().getDatabase(SaasConfig.mongoDBName);
	}

	/**
	 * 
	 * save(新增)
	 * 
	 * @param model
	 * @param json
	 * @return
	 * @throws Exception
	 *             String
	 * @exception
	 * @since 1.0.0
	 */
	public JSONObject save(String model, String json) throws ServiceException {
		//新增的集合进行按系统主键进行分片
		if(!db.collectionExists(model)){
			shardCollection(SaasConfig.mongoDBName, model, "_id");
		}
		long date = DateUtils.date24ToTimeStamp();
		DBObject dbObject = (DBObject) JSON.parse(json);
		ObjectId objectId = ObjectId.get();
		String id = objectId.toString();
		dbObject.put("_id", objectId);// 系统主键
		dbObject.put(Constant.OBJECTID, id);// 业务主键
		dbObject.put(Constant.CREATED_AT, date);
		dbObject.put(Constant.UPDATED_AT, date);
		DBCollection dbCollection = db.getCollection(model);
		WriteResult wr = dbCollection.insert(dbObject, WriteConcern.SAFE);
//		try {
//			cacheService.set(model+":"+id, dbObject.toString());
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
		JSONObject returnValue = new JSONObject();
		returnValue.put(Constant.OBJECTID, id);// 业务主键
		returnValue.put(Constant.CREATED_AT, date);
		returnValue.put(Constant.UPDATED_AT, date);
		return returnValue;
	}

	public void saveTemp(String model, String json) throws ServiceException {
		//新增的集合进行按系统主键进行分片
		if(!db.collectionExists(model)){
			shardCollection(SaasConfig.mongoDBName, model, "_id");
		}
		DBCollection dbCollection = db.getCollection(model);
		DBObject dbObject = (DBObject) JSON.parse(json);
		ObjectId objectId = new ObjectId(String.valueOf(dbObject.get(Constant.OBJECTID)));
		dbObject.put("_id", objectId);// 系统主键
		WriteResult wr = dbCollection.insert(dbObject, WriteConcern.SAFE);
	}

	
	/**
	 * 
	 * delete(删除)
	 * 
	 * @param model
	 * @param id
	 * @return String
	 * @exception
	 * @since 1.0.0
	 */
	public JSONObject delete(String model, String id) throws ServiceException{
		DBCollection dbCollection = db.getCollection(model);
		long date = DateUtils.date24ToTimeStamp();
		DBObject dbObject = new BasicDBObject(Constant.OBJECTID, id);
		WriteResult wr = dbCollection.remove(dbObject,WriteConcern.SAFE);
		if(wr.getN()<=0) {
			return null;
		}
//		try {
//			cacheService.del(model+":"+id);
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
		JSONObject returnValue = new JSONObject();
		returnValue.put(Constant.OBJECTID, id);// 业务主键
		returnValue.put(Constant.UPDATED_AT, date);
		return returnValue;
	}

	/**
	 * 
	 * update(更新)
	 * 
	 * @param model
	 * @param json
	 * @param id
	 * @return String
	 * @exception
	 * @since 1.0.0
	 */
	public JSONObject update(String model, String json, String id) throws ServiceException{
		DBCollection dbCollection = db.getCollection(model);
		DBObject updateDBObject = (DBObject) JSON.parse(json);
		long date = DateUtils.date24ToTimeStamp();
		updateDBObject.put(Constant.UPDATED_AT, date);
		WriteResult wr = dbCollection.update(new BasicDBObject(Constant.OBJECTID, id), new BasicDBObject("$set", updateDBObject),false,true,WriteConcern.SAFE);
		if(wr.getN()<=0 || !wr.isUpdateOfExisting()) {
			return null;
		}
//		try {
//			cacheService.del(model+":"+id);
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
		JSONObject returnValue = new JSONObject();
		returnValue.put(Constant.OBJECTID, id);// 业务主键
		returnValue.put(Constant.UPDATED_AT, date);
		return returnValue;
	}

	/**
	 * 
	 * 查找并更新
	 * findAndModifySet(更新)
	 * 使用时需要注意：
	 * 1.默认使用$set，其中包含$inc的处理
	 * 2.如果需要用到其他的mongodb的语法还需增加判断和处理
	 * 若不想修改次方法；可使用宝哥写的findAndModify；
	 * 从前台将需处理的字段填写好
	 * 
	 * @param model
	 * @param update
	 * @param where
	 * @return String
	 * @exception
	 * @since 1.0.0
	 */
	public JSONObject findAndModifySet(String model, String update, String where) throws ServiceException{
		DBCollection dbCollection = db.getCollection(model);
		DBObject updateDBObject = (DBObject) JSON.parse(update);
		DBObject ref = (DBObject) JSON.parse(where);
		if (ref.containsField(Constant.OBJECTID)&&DataUtils.isNotEmpty(ref.get(Constant.OBJECTID))) {
			ref.put("_id", new ObjectId(String.valueOf(ref.get(Constant.OBJECTID))));
			BasicDBObject basicDBObject = new BasicDBObject();
			if (updateDBObject.containsKey(MongoCondition.INC.getVal())) {
				basicDBObject.put(MongoCondition.INC.getVal(), updateDBObject.get(MongoCondition.INC.getVal()));
				updateDBObject.removeField(MongoCondition.INC.getVal());
			}
			long date = DateUtils.date24ToTimeStamp();
			updateDBObject.put(Constant.UPDATED_AT, date);
			basicDBObject.put("$set", updateDBObject);
			DBObject findAndModify = dbCollection.findAndModify(ref, null, null, false, basicDBObject, true, false);
			if (findAndModify==null) {
				return null;
			}
//			try {
//				cacheService.del(model+":"+findAndModify.get(Constant.OBJECTID));
//			} catch (Exception e) {
//				e.printStackTrace();
//			}
			return JSONObject.parseObject(findAndModify.toString());
		}else{
			throw new ServiceException("where条件必须包含"+Constant.OBJECTID+"字段");
		}
	}

	/**
	 * 
	 * inc(自增长)
	 * 
	 * @param model
	 * @param json
	 * @param id
	 * @return String
	 * @exception
	 * @since 1.0.0
	 */
	public JSONObject inc(String model, String json, String id) throws ServiceException{
		DBCollection dbCollection = db.getCollection(model);
		DBObject updateDBObject = (DBObject) JSON.parse(json);
		BasicDBObject query = new BasicDBObject(Constant.OBJECTID, id);
		query.put("_id", new ObjectId(id));
		DBObject findAndModify = dbCollection.findAndModify(query, new BasicDBObject("$inc", updateDBObject));
		if (findAndModify==null) {
			return null;
		}
		return JSONObject.parseObject(findAndModify.toString());
	}

	/**
	 * 聚合函数
	 * @param model 表
	 * @param query 查询条件
	 * @param sort 排序
	 * @param skip 分页
	 * @param limit 分页
	 * @return
	 * @throws Exception
	 */
	public JSONArray aggregate(String model, String query, String sort, int skip, int limit) throws Exception {
		if(StringUtils.isBlank(model)||StringUtils.isBlank(query)){
			return null;
		}
		DBCollection dbCollection = db.getCollection(model);
		BasicDBObject $query = (BasicDBObject)JSON.parse(query);
		BasicDBObject $skip = new BasicDBObject("$skip", skip);
		BasicDBObject $limit = new BasicDBObject("$limit", limit);
		List<BasicDBObject> list = new ArrayList<BasicDBObject>();
		list.add($query);
		if(StringUtils.isNotBlank(sort)){
			list.add((new BasicDBObject("$sort", (BasicDBObject)JSON.parse(sort))));
		}
		list.add($skip);
		list.add($limit);
		AggregationOutput out = dbCollection.aggregate(list);
		Iterator<DBObject> itor = out.results().iterator();
		JSONArray results = new JSONArray();
		while(itor.hasNext()){
			results.add(JSONObject.parseObject(itor.next().toString()));
		}
		return results;
	}
	/**
	 * 聚合函数
	 * aggregate:
	 *
	 * @author sid
	 * @param model
	 * @param conditions
	 * 通过管道操作；具体可用管道如下：
	 * $project：修改输入文档的结构。可以用来重命名、增加或删除域，也可以用于创建计算结果以及嵌套文档。
	 * $match：用于过滤数据，只输出符合条件的文档。$match使用MongoDB的标准查询操作。
	 * $limit：用来限制MongoDB聚合管道返回的文档数。
	 * $skip：在聚合管道中跳过指定数量的文档，并返回余下的文档。
	 * $unwind：将文档中的某一个数组类型字段拆分成多条，每条包含数组中的一个值。
	 * $group：将集合中的文档分组，可用于统计结果。
	 * $sort：将输入文档排序后输出。
	 * $geoNear：输出接近某一地理位置的有序文档。
	 * 下面是表达式内的可以函数
	 * $sum、$avg、$min、$max、$push、$addToSet、$first、$last
	 * @return
	 * @throws Exception
	 */
	public JSONArray aggregate(String model, List<String> conditions) {
		DBCollection dbCollection = db.getCollection(model);
		List<BasicDBObject> list = new ArrayList<BasicDBObject>();
		for (String str : conditions) {
			list.add((BasicDBObject)JSON.parse(str));
		}
		AggregationOutput out = dbCollection.aggregate(list);
		Iterator<DBObject> itor = out.results().iterator();
		JSONArray results = new JSONArray();
		while(itor.hasNext()){
			results.add(JSONObject.parseObject(itor.next().toString()));
		}
		return results;
	}
	/**
	 * 
	 * get(按id查找)
	 * 
	 * @param model
	 * @param id
	 * @return String
	 * @exception
	 * @since 1.0.0
	 */
	public String get(String model, String id) throws ServiceException{
		String cacheString = null;
//		String cacheString = cacheService.get(model+":"+id);
//		
//		if(StringUtils.isBlank(cacheString)){
			DBCollection dbCollection = db.getCollection(model);
			DBObject dbObject = dbCollection.findOne(new BasicDBObject(Constant.OBJECTID, id));
			if(dbObject!=null){
				cacheString = dbObject.toString();
			}
			
//			if(StringUtils.isNotBlank(cacheString)){
//				try {
//					cacheService.set(model+":"+id, cacheString);
//				} catch (Exception e) {
//					e.printStackTrace();
//				}
//			}
//		}
		if(StringUtils.isBlank(cacheString)){
			return null;
		}
		return cacheString;
	}

	/**
	 * login(用户登录,成功后返回用户信息)
	 * 
	 * @param model
	 * @param json
	 * @return DBObject
	 * @exception
	 * @since 1.0.0
	 */
	public DBObject login(String model, String json) throws Exception {
		DBCollection dbCollection = db.getCollection(model);
		DBObject query = (DBObject) JSON.parse(json);// 设置查询条件
		BasicDBObject queryCondition = new BasicDBObject();
		BasicDBList values = new BasicDBList();
		// (email=loginIdentifying or username=loginIdentifying or
		// mobilePhoneNumber=loginIdentifying) and password=password
		String loginIdentifying = (String) query.get(Constant.User_loginIdentifying);
		
		
		String  site = (String) query.get("site");
		if(site.matches("(qq|weibo|weixin|weixin_h5|weibo_h5|qq_h5)")){//第三方登陆
			if(site.equalsIgnoreCase("qq")) {
				values.add(new BasicDBObject("authData.qq.openid", loginIdentifying));
			}else if(site.equalsIgnoreCase("weibo")) {
				values.add(new BasicDBObject("authData.weibo.uid", loginIdentifying));
			}else if(site.equalsIgnoreCase("weixin")){
				values.add(new BasicDBObject("authData.weixin.openid", loginIdentifying));
				values.add(new BasicDBObject("authData.weixin.unionid", loginIdentifying));
			}else if(site.equalsIgnoreCase("qq_h5")) {
				values.add(new BasicDBObject("authData.qq.openid", loginIdentifying));
			}else if(site.equalsIgnoreCase("weibo_h5")) {
				values.add(new BasicDBObject("authData.weibo.uid", loginIdentifying));
			}else if(site.equalsIgnoreCase("weixin_h5")){
				values.add(new BasicDBObject("authData.weixin.openid", loginIdentifying));
				values.add(new BasicDBObject("authData.weixin.unionid", loginIdentifying));
			}
			queryCondition.put("$or", values);
			DBObject user = dbCollection.findOne(queryCondition);
			return user;
		}else{//本站用密码登陆
				values.add(new BasicDBObject(Constant.user_username, loginIdentifying));
				values.add(new BasicDBObject(Constant.user_phonenum, loginIdentifying));
				values.add(new BasicDBObject(Constant.user_email, loginIdentifying));
				values.add(new BasicDBObject(Constant.user_siteUserId, loginIdentifying));
				queryCondition.put("$or", values);
				DBObject user = dbCollection.findOne(queryCondition);
				if(user==null) return null;
				String salt = Constant.sha512_salt;
				if(user.containsField("salt")&&StringUtils.isNotBlank((String)user.get("salt"))){
					salt = (String)user.get("salt");
				}
//				String password = new Sha512Hash((String)query.get(Constant.user_password), salt, 513).toBase64();
				String password = Md5Util.encode((String)query.get(Constant.user_password)+(String)query.get(Constant.user_username));
				queryCondition.put(Constant.user_password, password);
				user = dbCollection.findOne(queryCondition);
				return user;
		}	
	}

	/**
	 * 
	 * find(高级查询)
	 * 
	 * @param where
	 *            查询条件
	 * @param fields
	 *            返回字段
	 * @param orderBy
	 *            排序条件
	 * @param collection
	 *            查询模型
	 * @param skip
	 *            跳过条数
	 * @param size
	 *            返回条数
	 * @return List<DBObject>
	 * @exception
	 * @since 1.0.0
	 */
	public DBObject find(String where, String fields, String orderBy, String collection, int skip, int size) {
		DBCollection dbCollection = db.getCollection(collection);
		DBObject ref = (DBObject) JSON.parse(where);
		DBObject keys = (DBObject) JSON.parse(fields);
		keys.put("_id", 0);//不返回系统主键
		DBObject order = (DBObject) JSON.parse(orderBy);
		DBCursor dbCursor = dbCollection.find(ref, keys).sort(order).skip(skip).limit(size);
		BasicDBList list = new BasicDBList();
		DBObject result = new BasicDBObject();
		while (dbCursor.hasNext()) {
			list.add(dbCursor.next());
		}
		result.put("count", dbCursor.count());
		result.put(Constant.RESULTS, list);
		return result;
	}

	/**
	 * 
	 * find(高级查询)
	 * 
	 * @param where
	 *            查询条件
	 * @param fields
	 *            返回字段
	 * @param orderBy
	 *            排序条件
	 * @param collection
	 *            查询模型
	 * @return List<DBObject>
	 * @exception
	 * @since 1.0.0
	 */
	public DBObject find(String where, String fields, String orderBy, String collection) {
		DBCollection dbCollection = db.getCollection(collection);
		DBObject ref = (DBObject) JSON.parse(where);
		DBObject keys = (DBObject) JSON.parse(fields);
		keys.put("_id", 0);//不返回系统主键
		DBObject order = (DBObject) JSON.parse(orderBy);
		DBCursor dbCursor = dbCollection.find(ref, keys).sort(order);
		BasicDBList list = new BasicDBList();
		DBObject result = new BasicDBObject();
		while (dbCursor.hasNext()) {
			list.add(dbCursor.next());
		}
		result.put("count", dbCursor.count());
		result.put(Constant.RESULTS, list);
		return result;
	}

	/**
	 * 
	 * distinct(高级查询)
	 * 
	 * @param query
	 *            查询条件
	 * @param fieldName
	 *            指定字段
	 * @param collection
	 *            查询模型
	 * @return List<DBObject>
	 * @exception
	 * @since 1.0.0
	 */
	public List<?> distinct(String query,String fieldName, String collection) {
		DBCollection dbCollection = db.getCollection(collection);
		DBObject q = (DBObject) JSON.parse(query);
		List<?> distincts = dbCollection.distinct(fieldName, q);
		return distincts;
	}

	/**
	 * 
	 * eval(在mongo Server上执行js函数)
	 * 
	 * @param function
	 * @param json
	 * @return String
	 * @exception
	 * @since 1.0.0
	 */
	public String eval(String function, String json) {
		// 使用javascript最好是预先定义好注册到数据库服务端，客户端传入函数名及参数即可
		String code = "function(obj){return "+function+"(obj);}";
		DBObject obj = (DBObject) JSON.parse(json);
		Object o = db.eval(code, obj);
		String value = null;
		if(o instanceof Boolean){
			value = String.valueOf(((Boolean)o).booleanValue());
		}else if(o instanceof Number){
			value = String.valueOf((Number)o);
		}else if(o instanceof String){
			value = String.valueOf(o);
		}else if(o instanceof BasicDBObject){
			value = ((BasicDBObject)o).toString();
		}else if(o instanceof BasicDBList){
			value = ((BasicDBList)o).toString();
		}
		return value;
	}
	
	/**
	 * 原子修改操作
	 * @param model
	 * @param where
	 * @param update
	 * @param returnNew
	 * @param upsert
	 * @return
	 */
	public JSONObject findAndModify(String model, String where, String update, boolean returnNew, boolean upsert){
		DBCollection dbCollection = db.getCollection(model);
		DBObject query = (DBObject) JSON.parse(where);
		if(query.containsField(Constant.OBJECTID)){
			query.put("_id", new ObjectId(String.valueOf(query.get(Constant.OBJECTID))));
		}
		DBObject updateObject = (DBObject) JSON.parse(update);
		DBObject result = dbCollection.findAndModify(query, null, null, false, updateObject, returnNew, upsert);
		if (result == null) {
			return null;
		}
//		try {
//			cacheService.del(model+":"+result.get(Constant.OBJECTID));
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
		return JSONObject.parseObject(result.toString());
	}

	// //////////////////////////////////////////////////////////////////////
	// /////////////////////BasicMongoDBManager//////////////////////////////
	// //////////////////////////////////////////////////////////////////////
	// //////////////////////////////////////////////////////////////////////

	/**
	 * 测试: 查询所有数据
	 * 
	 * private DBCursor queryAll(String collection) { DBCollection dbCollection
	 * = db.getCollection(collection); return dbCollection.find(); }
	 */

	/**
	 * 
	 * 添加集
	 * 
	 * public WriteResult add(String collection, List<DBObject> list) {
	 * DBCollection dbCollection = db.getCollection(collection); return
	 * dbCollection.insert(list).getN(); }
	 */

	/**
	 * 插入
	 * 
	 * @param collection
	 *            集合名
	 * @param obj
	 *            新文档
	 */

	public WriteResult insert(String collection, DBObject obj) {
		DBCollection dbCollection = db.getCollection(collection);
		return dbCollection.insert(obj);
	}

	/**
	 * 更新
	 * 
	 * @param collection
	 *            集合名
	 * @param dbDoc
	 *            更新目标条件
	 * @param newDoc
	 *            新文档
	 * @param insertAllow
	 *            查找无效后是否可插入
	 * @param multiAlter
	 *            是否可以多条更新
	 */

	public WriteResult update(String collection, DBObject dbDoc, DBObject newDoc, boolean insertAllow, boolean multiAlter) {
		DBCollection dbCollection = db.getCollection(collection);
		return dbCollection.update(dbDoc, newDoc, insertAllow, multiAlter);
	}

	/**
	 * 删除
	 * 
	 * @param collection
	 *            集合名
	 * @param delObj
	 *            删除目标条件
	 */
	public void delete(String collection, DBObject delObj) {
		DBCollection dbCollection = db.getCollection(collection);
		dbCollection.remove(delObj);
	}

	/**
	 * 查询
	 * 
	 * @param collection
	 *            集合名
	 * @param obj
	 *            条件文档
	 * @return DBCursor 游标
	 */
	public DBCursor find(String collection, DBObject obj) {
		DBCollection dbCollection = db.getCollection(collection);
		
		return dbCollection.find(obj);
	}

	/**
	 * 查询unique
	 * 
	 * @param collection
	 *            集合名
	 * @param obj
	 *            条件文档
	 * @return DBObject 文档对象
	 */
	public DBObject findOne(String collection, DBObject obj) {
		DBCollection dbCollection = db.getCollection(collection);
		return dbCollection.findOne(obj);
	}

	/**
	 * 查询
	 * 
	 * @param collection
	 *            集合名
	 * @param key
	 *            查询条件键
	 * @param value
	 *            查询条件值
	 * @return DBCursor 游标
	 */
	public DBCursor find(String collection, String key, String value) {
		DBObject query = new BasicDBObject();
		query.put(key, value);
		DBCollection dbCollection = db.getCollection(collection);
		return dbCollection.find(query);
	}

	/**
	 * 查询unique
	 * 
	 * @param collection
	 *            集合名
	 * @param key
	 *            查询条件键
	 * @param value
	 *            查询条件值
	 * @return DBObject 文档对象
	 */
	public DBObject findOne(String collection, String key, String value) {
		DBObject query = new BasicDBObject();
		query.put(key, value);
		DBCollection dbCollection = db.getCollection(collection);
		return dbCollection.findOne(query);
	}
	
	//对集合启动分片，前提是所在数据库已启动分片
	private boolean shardCollection(String dbname,String collection,String key){
		if (MongoDBConnection.isShard) {
			logger.info("新的集合"+collection+"启动分片");
			MongoDatabase admin = MongoDBConnection.getMongoClient().getDatabase("admin");
			BasicDBObject cmd = new BasicDBObject();
			BasicDBObject shardKeys = new BasicDBObject();
			shardKeys.put("_id",  1);shardKeys.put(Constant.OBJECTID,  1);
			cmd.put("shardcollection", dbname+"."+collection);
			cmd.put("key", shardKeys);
			Document result = admin.runCommand(cmd);
			logger.info("mongodb cmd:"+cmd);
			logger.info("document:"+result.toJson());
			if(result==null||result.isEmpty()){
				return false;
			}else if(result.getDouble("ok")>0){
				return true;
			}
		}
		return false;
	}
	
	//对数据库启动分片
	private boolean enableSharding(String dbname){
		MongoDatabase admin = MongoDBConnection.getMongoClient().getDatabase("admin");
		BasicDBObject cmd = new BasicDBObject();
		cmd.put("enablesharding", dbname+"."+dbname);
		Document result = admin.runCommand(cmd);
		logger.info("mongodb cmd:"+cmd);
		logger.info("document:"+result.toJson());
		if(result==null||result.isEmpty()){
			return false;
		}else if(result.getDouble("ok")>0){
			return true;
		}
		return false;
	}

}
